package org.gdstash.db;

import org.gdstash.file.ARZRecord;
import org.gdstash.file.DDSLoader;
import org.gdstash.file.GDParseException;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class DBBitmap {
    public static final String TABLE_NAME = "GDC_BITMAP";
    private static final int ROW_RECORD_ID = 1;
    private static final int ROW_BITMAP_ID = 2;
    private static final int ROW_BITMAP = 3;
    private static final int ROW_POS_X = 4;
    private static final int ROW_POS_Y = 5;
    private static ConcurrentHashMap<String, DBBitmap> hashBuffer;
    private String recordID;
    private String bitmapID;
    private byte[] bitmap;
    private int posX;
    private int posY;
    private BufferedImage image;

    public DBBitmap() {
        this.recordID = null;
        this.bitmapID = null;
        this.bitmap = null;
        this.posX = 0;
        this.posY = 0;
        this.image = null;
    }

    public DBBitmap(final ARZRecord record) {
        this();
        this.recordID = record.getFileName();
        this.bitmapID = record.getBitmapID();
        this.posX = record.getPosX();
        this.posY = record.getPosY();
    }

    static {
        DBBitmap.hashBuffer = new ConcurrentHashMap<String, DBBitmap>();
    }

    public BufferedImage getImage() {
        return this.image;
    }

    public int getPosX() {
        return this.posX;
    }

    public static void clearBuffer() {
        DBBitmap.hashBuffer.clear();
    }

    public static void createTable() throws SQLException {
        final String dropTable = "DROP TABLE GDC_BITMAP";
        final String createTable = "CREATE TABLE GDC_BITMAP (RECORD_ID        VARCHAR(256) NOT NULL, BITMAP_ID        VARCHAR(256), BITMAP           BLOB(32M), POS_X            INTEGER, POS_Y            INTEGER, PRIMARY KEY (RECORD_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GDC_BITMAP")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GDC_BITMAP"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBBitmap entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBBitmap bitmap = new DBBitmap(record);
        final String insertBitmap = "INSERT INTO GDC_BITMAP VALUES (?,?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insertBitmap)) {
                ps.setString(1, bitmap.recordID);
                ps.setString(2, bitmap.bitmapID);
                if (bitmap.bitmap == null) {
                    ps.setBlob(3, (Blob) null);
                } else {
                    ps.setBlob(3, new ByteArrayInputStream(bitmap.bitmap));
                }
                ps.setInt(4, bitmap.posX);
                ps.setInt(5, bitmap.posY);
                ps.executeUpdate();
                ps.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {record.getFileName(), "GDC_BITMAP"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addLowError(msg);
                GDMsgLogger.addLowError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
            throw ex2;
        }
    }

    public static DBBitmap get(final String recordID) {
        DBBitmap bitmap = null;
        bitmap = DBBitmap.hashBuffer.get(recordID);
        if (bitmap == null) {
            bitmap = getDB(recordID);
        }
        return bitmap;
    }

    private static DBBitmap getDB(final String recordID) {
        DBBitmap bitmap = null;
        final String command = "SELECT * FROM GDC_BITMAP WHERE RECORD_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, recordID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBBitmap> list = wrap(rs);
                if (list.isEmpty()) {
                    bitmap = null;
                } else {
                    bitmap = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {recordID, "GDC_BITMAP"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return bitmap;
    }

    private static List<DBBitmap> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBBitmap> list = new LinkedList<DBBitmap>();
        Blob blob = null;
        while (rs.next()) {
            final DBBitmap bitmap = new DBBitmap();
            bitmap.recordID = rs.getString(1);
            final DBBitmap buff = DBBitmap.hashBuffer.get(bitmap.recordID);
            if (buff != null) {
                list.add(buff);
            } else {
                bitmap.bitmapID = rs.getString(2);
                blob = rs.getBlob(3);
                if (blob == null) {
                    bitmap.bitmap = null;
                } else {
                    bitmap.bitmap = blob.getBytes(1L, (int) blob.length());
                }
                if (bitmap.bitmap != null) {
                    try {
                        bitmap.image = DDSLoader.getImage(bitmap.bitmap);
                    } catch (GDParseException ex) {
                        final Object[] args = {bitmap.recordID};
                        final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_BITMAP_DECODE_FAILED", args);
                        GDMsgLogger.addError(msg);
                        bitmap.image = null;
                    }
                }
                bitmap.posX = rs.getInt(4);
                bitmap.posY = rs.getInt(5);
                list.add(bitmap);
                DBBitmap.hashBuffer.put(bitmap.recordID, bitmap);
            }
        }
        return list;
    }

    public static List<ImageInfo> getImageInfos(final String id) {
        final List<ImageInfo> list = new LinkedList<ImageInfo>();
        Blob blob = null;
        final String command = "SELECT RECORD_ID, BITMAP_ID, BITMAP FROM GDC_BITMAP WHERE (BITMAP_ID LIKE '" + id + "%' AND BITMAP IS NULL)";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            try (final ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    final ImageInfo info = new ImageInfo();
                    info.recordID = rs.getString(1);
                    info.bitmapID = rs.getString(2);
                    blob = rs.getBlob(3);
                    if (blob == null) {
                        info.bitmap = null;
                    } else {
                        info.bitmap = blob.getBytes(1L, (int) blob.length());
                    }
                    list.add(info);
                }
                conn.commit();
            } catch (Exception ex) {
                throw ex;
            }
        } catch (Exception ex2) {
            GDMsgLogger.addError(ex2);
        }
        return list;
    }

    public static void deleteNoImageBitmaps() {
        final String command = "DELETE FROM GDC_BITMAP WHERE BITMAP IS NULL";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.execute();
            conn.commit();
        } catch (Exception ex) {
            GDMsgLogger.addError(ex);
        }
    }

    public static void updateImageInfo(final List<ImageInfo> list) {
        if (list == null) {
            return;
        }
        final String command = "UPDATE GDC_BITMAP SET BITMAP = ? WHERE RECORD_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            for (final ImageInfo info : list) {
                try (final PreparedStatement ps = conn.prepareStatement(command)) {
                    if (info.bitmap != null) {
                        ps.setBlob(1, new ByteArrayInputStream(info.bitmap));
                        ps.setString(2, info.recordID);
                        ps.executeUpdate();
                        ps.close();
                        conn.commit();
                    }
                } catch (SQLException ex) {
                    conn.rollback();
                    final Object[] args = {info.recordID};
                    final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_IN_ITEM_IMAGE_SIZE", args);
                    GDMsgLogger.addWarning(msg);
                    GDMsgLogger.addWarning(ex);
                }
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(ex2);
        }
    }

    public int getPosY() {
        return this.posY;
    }

    public static class ImageInfo {
        public String recordID;
        public String bitmapID;
        public byte[] bitmap;
    }
}
